🎖️GitЯра🎖️
.specify/scripts/bash/create-new-feature.sh b2a8377bbb01a5875eba4b12f6f1c7e7d9e60a91 (b2a8377b) Text, 15.91 KB
T8b949e#!/usr/bin/env bash
Tffa657set -e
Te6edf3JSON_MODETff7b72=Tffa657false
Te6edf3DRY_RUNTff7b72=Tffa657false
Te6edf3ALLOW_EXISTINGTff7b72=Tffa657false
Te6edf3SHORT_NAMETff7b72=Ta5d6ff""
Te6edf3BRANCH_NUMBERTff7b72=Ta5d6ff""
Te6edf3USE_TIMESTAMPTff7b72=Tffa657false
Te6edf3ARGSTff7b72=Tff7b72(Tff7b72)
Te6edf3iTff7b72=T79c0ff1
Tff7b72while Tff7b72[ Te6edf3$i -le Te6edf3$# Tff7b72]Tb4b4b4; Tff7b72do
Te6edf3argTff7b72=Ta5d6ff"Tffd700${Tb4b4b4!iTffd700}Ta5d6ff"
Tff7b72case Ta5d6ff"Te6edf3$argTa5d6ff" Tff7b72in
--jsonTff7b72)
Te6edf3JSON_MODETff7b72=Tffa657true
Tb4b4b4;Tb4b4b4;
--dry-runTff7b72)
Te6edf3DRY_RUNTff7b72=Tffa657true
Tb4b4b4;Tb4b4b4;
--allow-existing-branchTff7b72)
Te6edf3ALLOW_EXISTINGTff7b72=Tffa657true
Tb4b4b4;Tb4b4b4;
--short-nameTff7b72)
Tff7b72if Tff7b72[ Tff7b72$((Te6edf3i Tff7b72+ T79c0ff1Tff7b72)) -gt Te6edf3$# Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff'Error: --short-name requires a value' >Tb4b4b4&T79c0ff2
Tffa657exit T79c0ff1
Tff7b72fi
Te6edf3iTff7b72=Tff7b72$((Te6edf3i Tff7b72+ T79c0ff1Tff7b72))
Te6edf3next_argTff7b72=Ta5d6ff"Tffd700${Tb4b4b4!iTffd700}Ta5d6ff"
T8b949e# Check if the next argument is another option (starts with --)
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$next_argTa5d6ff" Tff7b72=Tff7b72= --* Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff'Error: --short-name requires a value' >Tb4b4b4&T79c0ff2
Tffa657exit T79c0ff1
Tff7b72fi
Te6edf3SHORT_NAMETff7b72=Ta5d6ff"Te6edf3$next_argTa5d6ff"
Tb4b4b4;Tb4b4b4;
--numberTff7b72)
Tff7b72if Tff7b72[ Tff7b72$((Te6edf3i Tff7b72+ T79c0ff1Tff7b72)) -gt Te6edf3$# Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff'Error: --number requires a value' >Tb4b4b4&T79c0ff2
Tffa657exit T79c0ff1
Tff7b72fi
Te6edf3iTff7b72=Tff7b72$((Te6edf3i Tff7b72+ T79c0ff1Tff7b72))
Te6edf3next_argTff7b72=Ta5d6ff"Tffd700${Tb4b4b4!iTffd700}Ta5d6ff"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$next_argTa5d6ff" Tff7b72=Tff7b72= --* Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff'Error: --number requires a value' >Tb4b4b4&T79c0ff2
Tffa657exit T79c0ff1
Tff7b72fi
Te6edf3BRANCH_NUMBERTff7b72=Ta5d6ff"Te6edf3$next_argTa5d6ff"
Tb4b4b4;Tb4b4b4;
--timestampTff7b72)
Te6edf3USE_TIMESTAMPTff7b72=Tffa657true
Tb4b4b4;Tb4b4b4;
--helpTb4b4b4|-hTff7b72)
Tffa657echo Ta5d6ff"Ta5d6ffUsage: Te6edf3$0Ta5d6ff [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>Ta5d6ff"
Tffa657echo Ta5d6ff""
Tffa657echo Ta5d6ff"Options:"
Tffa657echo Ta5d6ff" --json Output in JSON format"
Tffa657echo Ta5d6ff" --dry-run Compute branch name and paths without creating branches, directories, or files"
Tffa657echo Ta5d6ff" --allow-existing-branch Switch to branch if it already exists instead of failing"
Tffa657echo Ta5d6ff" --short-name <name> Provide a custom short name (2-4 words) for the branch"
Tffa657echo Ta5d6ff" --number N Specify branch number manually (overrides auto-detection)"
Tffa657echo Ta5d6ff" --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
Tffa657echo Ta5d6ff" --help, -h Show this help message"
Tffa657echo Ta5d6ff""
Tffa657echo Ta5d6ff"Examples:"
Tffa657echo Ta5d6ff"Ta5d6ff Te6edf3$0Ta5d6ff 'Add user authentication system' --short-name 'user-auth'Ta5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ff Te6edf3$0Ta5d6ff 'Implement OAuth2 integration for API' --number 5Ta5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ff Te6edf3$0Ta5d6ff --timestamp --short-name 'user-auth' 'Add user authentication'Ta5d6ff"
Tffa657exit T79c0ff0
Tb4b4b4;Tb4b4b4;
*Tff7b72)
Te6edf3ARGSTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$argTa5d6ff"Tff7b72)
Tb4b4b4;Tb4b4b4;
Tff7b72esac
Te6edf3iTff7b72=Tff7b72$((Te6edf3i Tff7b72+ T79c0ff1Tff7b72))
Tff7b72done
Te6edf3FEATURE_DESCRIPTIONTff7b72=Ta5d6ff"Tffd700${Te6edf3ARGSTb4b4b4[*]Tffd700}Ta5d6ff"
Tff7b72if Tff7b72[ -z Ta5d6ff"Te6edf3$FEATURE_DESCRIPTIONTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff"Ta5d6ffUsage: Te6edf3$0Ta5d6ff [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>Ta5d6ff" >Tb4b4b4&T79c0ff2
Tffa657exit T79c0ff1
Tff7b72fi
T8b949e# Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
Te6edf3FEATURE_DESCRIPTIONTff7b72=Tff7b72$(Tffa657echo Ta5d6ff"Te6edf3$FEATURE_DESCRIPTIONTa5d6ff" Tb4b4b4| sed -E Ta5d6ff's/^[[:space:]]+|[[:space:]]+$//g'Tff7b72)
Tff7b72if Tff7b72[ -z Ta5d6ff"Te6edf3$FEATURE_DESCRIPTIONTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff"Error: Feature description cannot be empty or contain only whitespace" >Tb4b4b4&T79c0ff2
Tffa657exit T79c0ff1
Tff7b72fi
T8b949e# Function to get highest number from specs directory
get_highest_from_specsTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3specs_dirTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3highestTff7b72=T79c0ff0
Tff7b72if Tff7b72[ -d Ta5d6ff"Te6edf3$specs_dirTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72for dir Tff7b72in Ta5d6ff"Te6edf3$specs_dirTa5d6ff"/*Tb4b4b4; Tff7b72do
Tff7b72[ -d Ta5d6ff"Te6edf3$dirTa5d6ff" Tff7b72] Tff7b72|| Tff7b72continue
Te6edf3dirnameTff7b72=Tff7b72$(basename Ta5d6ff"Te6edf3$dirTa5d6ff"Tff7b72)
T8b949e# Match sequential prefixes (>=3 digits), but skip timestamp dirs.
Tff7b72if Tffa657echo Ta5d6ff"Te6edf3$dirnameTa5d6ff" Tb4b4b4| grep -Eq Ta5d6ff'^[0-9]{3,}-' Tff7b72&& ! Tffa657echo Ta5d6ff"Te6edf3$dirnameTa5d6ff" Tb4b4b4| grep -Eq Ta5d6ff'^[0-9]{8}-[0-9]{6}-'Tb4b4b4; Tff7b72then
Te6edf3numberTff7b72=Tff7b72$(Tffa657echo Ta5d6ff"Te6edf3$dirnameTa5d6ff" Tb4b4b4| grep -Eo Ta5d6ff'^[0-9]+'Tff7b72)
Te6edf3numberTff7b72=Tff7b72$((T79c0ff10#Te6edf3$numberTff7b72))
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$numberTa5d6ff" -gt Ta5d6ff"Te6edf3$highestTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3highestTff7b72=Te6edf3$number
Tff7b72fi
Tff7b72fi
Tff7b72done
Tff7b72fi
Tffa657echo Ta5d6ff"Te6edf3$highestTa5d6ff"
Tff7b72}
T8b949e# Function to get highest number from git branches
get_highest_from_branchesTff7b72(Tff7b72) Tff7b72{
git branch -a T79c0ff2>/dev/null Tb4b4b4| sed Ta5d6ff's/^[* ]*//; s|^remotes/[^/]*/||' Tb4b4b4| _extract_highest_number
Tff7b72}
T8b949e# Extract the highest sequential feature number from a list of ref names (one per line).
T8b949e# Shared by get_highest_from_branches and get_highest_from_remote_refs.
_extract_highest_numberTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3highestTff7b72=T79c0ff0
Tff7b72while Te6edf3IFSTff7b72= Tffa657read -r nameTb4b4b4; Tff7b72do
Tff7b72[ -z Ta5d6ff"Te6edf3$nameTa5d6ff" Tff7b72] Tff7b72&& Tff7b72continue
Tff7b72if Tffa657echo Ta5d6ff"Te6edf3$nameTa5d6ff" Tb4b4b4| grep -Eq Ta5d6ff'^[0-9]{3,}-' Tff7b72&& ! Tffa657echo Ta5d6ff"Te6edf3$nameTa5d6ff" Tb4b4b4| grep -Eq Ta5d6ff'^[0-9]{8}-[0-9]{6}-'Tb4b4b4; Tff7b72then
Te6edf3numberTff7b72=Tff7b72$(Tffa657echo Ta5d6ff"Te6edf3$nameTa5d6ff" Tb4b4b4| grep -Eo Ta5d6ff'^[0-9]+' Tff7b72|| Tffa657echo Ta5d6ff"0"Tff7b72)
Te6edf3numberTff7b72=Tff7b72$((T79c0ff10#Te6edf3$numberTff7b72))
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$numberTa5d6ff" -gt Ta5d6ff"Te6edf3$highestTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3highestTff7b72=Te6edf3$number
Tff7b72fi
Tff7b72fi
Tff7b72done
Tffa657echo Ta5d6ff"Te6edf3$highestTa5d6ff"
Tff7b72}
T8b949e# Function to get highest number from remote branches without fetching (side-effect-free)
get_highest_from_remote_refsTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3highestTff7b72=T79c0ff0
Tff7b72for remote Tff7b72in Tff7b72$(git remote T79c0ff2>/dev/nullTff7b72)Tb4b4b4; Tff7b72do
Tffa657local remote_highest
Te6edf3remote_highestTff7b72=Tff7b72$(Te6edf3GIT_TERMINAL_PROMPTTff7b72=T79c0ff0 git ls-remote --heads Ta5d6ff"Te6edf3$remoteTa5d6ff" T79c0ff2>/dev/null Tb4b4b4| sed Ta5d6ff's|.*refs/heads/||' Tb4b4b4| _extract_highest_numberTff7b72)
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$remote_highestTa5d6ff" -gt Ta5d6ff"Te6edf3$highestTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3highestTff7b72=Te6edf3$remote_highest
Tff7b72fi
Tff7b72done
Tffa657echo Ta5d6ff"Te6edf3$highestTa5d6ff"
Tff7b72}
T8b949e# Function to check existing branches (local and remote) and return next available number.
T8b949e# When skip_fetch is true, queries remotes via ls-remote (read-only) instead of fetching.
check_existing_branchesTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3specs_dirTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3skip_fetchTff7b72=Ta5d6ff"Tffd700${Te6edf32Tff7b72:-Te6edf3falseTffd700}Ta5d6ff"
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$skip_fetchTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Side-effect-free: query remotes via ls-remote
Tffa657local Te6edf3highest_remoteTff7b72=Tff7b72$(get_highest_from_remote_refsTff7b72)
Tffa657local Te6edf3highest_branchTff7b72=Tff7b72$(get_highest_from_branchesTff7b72)
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$highest_remoteTa5d6ff" -gt Ta5d6ff"Te6edf3$highest_branchTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3highest_branchTff7b72=Te6edf3$highest_remote
Tff7b72fi
Tff7b72else
T8b949e# Fetch all remotes to get latest branch info (suppress errors if no remotes)
git fetch --all --prune >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1 Tff7b72|| Tffa657true
Tffa657local Te6edf3highest_branchTff7b72=Tff7b72$(get_highest_from_branchesTff7b72)
Tff7b72fi
T8b949e# Get highest number from ALL specs (not just matching short name)
Tffa657local Te6edf3highest_specTff7b72=Tff7b72$(get_highest_from_specs Ta5d6ff"Te6edf3$specs_dirTa5d6ff"Tff7b72)
T8b949e# Take the maximum of both
Tffa657local Te6edf3max_numTff7b72=Te6edf3$highest_branch
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$highest_specTa5d6ff" -gt Ta5d6ff"Te6edf3$max_numTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3max_numTff7b72=Te6edf3$highest_spec
Tff7b72fi
T8b949e# Return next number
Tffa657echo Tff7b72$((Te6edf3max_num Tff7b72+ T79c0ff1Tff7b72))
Tff7b72}
T8b949e# Function to clean and format a branch name
clean_branch_nameTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3nameTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657echo Ta5d6ff"Te6edf3$nameTa5d6ff" Tb4b4b4| tr Ta5d6ff'[:upper:]' Ta5d6ff'[:lower:]' Tb4b4b4| sed Ta5d6ff's/[^a-z0-9]/-/g' Tb4b4b4| sed Ta5d6ff's/-\+/-/g' Tb4b4b4| sed Ta5d6ff's/^-//' Tb4b4b4| sed Ta5d6ff's/-$//'
Tff7b72}
T8b949e# Resolve repository root using common.sh functions which prioritize .specify over git
Te6edf3SCRIPT_DIRTff7b72=Ta5d6ff"Tff7b72$(Te6edf3CDPATHTff7b72=Ta5d6ff"" Tffa657cd Ta5d6ff"Tff7b72$(dirname Ta5d6ff"Tffd700${Te6edf3BASH_SOURCETb4b4b4[0]Tffd700}Ta5d6ff"Tff7b72)Ta5d6ff" Tff7b72&& Tffa657pwdTff7b72)Ta5d6ff"
Tffa657source Ta5d6ff"Te6edf3$SCRIPT_DIRTa5d6ff/common.shTa5d6ff"
Te6edf3REPO_ROOTTff7b72=Tff7b72$(get_repo_rootTff7b72)
T8b949e# Check if git is available at this repo root (not a parent)
Tff7b72if has_gitTb4b4b4; Tff7b72then
Te6edf3HAS_GITTff7b72=Tffa657true
Tff7b72else
Te6edf3HAS_GITTff7b72=Tffa657false
Tff7b72fi
Tffa657cd Ta5d6ff"Te6edf3$REPO_ROOTTa5d6ff"
Te6edf3SPECS_DIRTff7b72=Ta5d6ff"Te6edf3$REPO_ROOTTa5d6ff/specsTa5d6ff"
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" !Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
mkdir -p Ta5d6ff"Te6edf3$SPECS_DIRTa5d6ff"
Tff7b72fi
T8b949e# Function to generate branch name with stop word filtering and length filtering
generate_branch_nameTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3descriptionTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
T8b949e# Common stop words to filter out
Tffa657local Te6edf3stop_wordsTff7b72=Ta5d6ff"Ta5d6ff^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$Ta5d6ff"
T8b949e# Convert to lowercase and split into words
Tffa657local Te6edf3clean_nameTff7b72=Tff7b72$(Tffa657echo Ta5d6ff"Te6edf3$descriptionTa5d6ff" Tb4b4b4| tr Ta5d6ff'[:upper:]' Ta5d6ff'[:lower:]' Tb4b4b4| sed Ta5d6ff's/[^a-z0-9]/ /g'Tff7b72)
T8b949e# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
Tffa657local Te6edf3meaningful_wordsTff7b72=Tff7b72(Tff7b72)
Tff7b72for word Tff7b72in Te6edf3$clean_nameTb4b4b4; Tff7b72do
T8b949e# Skip empty words
Tff7b72[ -z Ta5d6ff"Te6edf3$wordTa5d6ff" Tff7b72] Tff7b72&& Tff7b72continue
T8b949e# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
Tff7b72if ! Tffa657echo Ta5d6ff"Te6edf3$wordTa5d6ff" Tb4b4b4| grep -qiE Ta5d6ff"Te6edf3$stop_wordsTa5d6ff"Tb4b4b4; Tff7b72then
Tff7b72if Tff7b72[ Tffd700${#Te6edf3wordTffd700} -ge T79c0ff3 Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3meaningful_wordsTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$wordTa5d6ff"Tff7b72)
Tff7b72elif Tffa657echo Ta5d6ff"Te6edf3$descriptionTa5d6ff" Tb4b4b4| grep -q Ta5d6ff"Ta5d6ff\bTffd700${Te6edf3wordTb4b4b4^^Tffd700}Ta5d6ff\bTa5d6ff"Tb4b4b4; Tff7b72then
T8b949e# Keep short words if they appear as uppercase in original (likely acronyms)
Te6edf3meaningful_wordsTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$wordTa5d6ff"Tff7b72)
Tff7b72fi
Tff7b72fi
Tff7b72done
T8b949e# If we have meaningful words, use first 3-4 of them
Tff7b72if Tff7b72[ Tffd700${#Te6edf3meaningful_wordsTb4b4b4[@]Tffd700} -gt T79c0ff0 Tff7b72]Tb4b4b4; Tff7b72then
Tffa657local Te6edf3max_wordsTff7b72=T79c0ff3
Tff7b72if Tff7b72[ Tffd700${#Te6edf3meaningful_wordsTb4b4b4[@]Tffd700} -eq T79c0ff4 Tff7b72]Tb4b4b4; Tff7b72then Te6edf3max_wordsTff7b72=T79c0ff4Tb4b4b4; Tff7b72fi
Tffa657local Te6edf3resultTff7b72=Ta5d6ff""
Tffa657local Te6edf3countTff7b72=T79c0ff0
Tff7b72for word Tff7b72in Ta5d6ff"Tffd700${Te6edf3meaningful_wordsTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72if Tff7b72[ Te6edf3$count -ge Te6edf3$max_words Tff7b72]Tb4b4b4; Tff7b72then Tff7b72breakTb4b4b4; Tff7b72fi
Tff7b72if Tff7b72[ -n Ta5d6ff"Te6edf3$resultTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then Te6edf3resultTff7b72=Ta5d6ff"Te6edf3$resultTa5d6ff-Ta5d6ff"Tb4b4b4; Tff7b72fi
Te6edf3resultTff7b72=Ta5d6ff"Te6edf3$resultTe6edf3$wordTa5d6ff"
Te6edf3countTff7b72=Tff7b72$((Te6edf3count Tff7b72+ T79c0ff1Tff7b72))
Tff7b72done
Tffa657echo Ta5d6ff"Te6edf3$resultTa5d6ff"
Tff7b72else
T8b949e# Fallback to original logic if no meaningful words found
Tffa657local Te6edf3cleanedTff7b72=Tff7b72$(clean_branch_name Ta5d6ff"Te6edf3$descriptionTa5d6ff"Tff7b72)
Tffa657echo Ta5d6ff"Te6edf3$cleanedTa5d6ff" Tb4b4b4| tr Ta5d6ff'-' Ta5d6ff'\n' Tb4b4b4| grep -v Ta5d6ff'^$' Tb4b4b4| head -3 Tb4b4b4| tr Ta5d6ff'\n' Ta5d6ff'-' Tb4b4b4| sed Ta5d6ff's/-$//'
Tff7b72fi
Tff7b72}
T8b949e# Generate branch name
Tff7b72if Tff7b72[ -n Ta5d6ff"Te6edf3$SHORT_NAMETa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Use provided short name, just clean it up
Te6edf3BRANCH_SUFFIXTff7b72=Tff7b72$(clean_branch_name Ta5d6ff"Te6edf3$SHORT_NAMETa5d6ff"Tff7b72)
Tff7b72else
T8b949e# Generate from description with smart filtering
Te6edf3BRANCH_SUFFIXTff7b72=Tff7b72$(generate_branch_name Ta5d6ff"Te6edf3$FEATURE_DESCRIPTIONTa5d6ff"Tff7b72)
Tff7b72fi
T8b949e# Warn if --number and --timestamp are both specified
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$USE_TIMESTAMPTa5d6ff" Tff7b72= Tffa657true Tff7b72] Tff7b72&& Tff7b72[ -n Ta5d6ff"Te6edf3$BRANCH_NUMBERTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"[specify] Warning: --number is ignored when --timestamp is used"
Te6edf3BRANCH_NUMBERTff7b72=Ta5d6ff""
Tff7b72fi
T8b949e# Determine branch prefix
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$USE_TIMESTAMPTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3FEATURE_NUMTff7b72=Tff7b72$(date +%Y%m%d-%H%M%STff7b72)
Te6edf3BRANCH_NAMETff7b72=Ta5d6ff"Tffd700${Te6edf3FEATURE_NUMTffd700}Ta5d6ff-Tffd700${Te6edf3BRANCH_SUFFIXTffd700}Ta5d6ff"
Tff7b72else
T8b949e# Determine branch number
Tff7b72if Tff7b72[ -z Ta5d6ff"Te6edf3$BRANCH_NUMBERTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" Tff7b72= Tffa657true Tff7b72] Tff7b72&& Tff7b72[ Ta5d6ff"Te6edf3$HAS_GITTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Dry-run: query remotes via ls-remote (side-effect-free, no fetch)
Te6edf3BRANCH_NUMBERTff7b72=Tff7b72$(check_existing_branches Ta5d6ff"Te6edf3$SPECS_DIRTa5d6ff" Tffa657trueTff7b72)
Tff7b72elif Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Dry-run without git: local spec dirs only
Te6edf3HIGHESTTff7b72=Tff7b72$(get_highest_from_specs Ta5d6ff"Te6edf3$SPECS_DIRTa5d6ff"Tff7b72)
Te6edf3BRANCH_NUMBERTff7b72=Tff7b72$((Te6edf3HIGHEST Tff7b72+ T79c0ff1Tff7b72))
Tff7b72elif Tff7b72[ Ta5d6ff"Te6edf3$HAS_GITTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Check existing branches on remotes
Te6edf3BRANCH_NUMBERTff7b72=Tff7b72$(check_existing_branches Ta5d6ff"Te6edf3$SPECS_DIRTa5d6ff"Tff7b72)
Tff7b72else
T8b949e# Fall back to local directory check
Te6edf3HIGHESTTff7b72=Tff7b72$(get_highest_from_specs Ta5d6ff"Te6edf3$SPECS_DIRTa5d6ff"Tff7b72)
Te6edf3BRANCH_NUMBERTff7b72=Tff7b72$((Te6edf3HIGHEST Tff7b72+ T79c0ff1Tff7b72))
Tff7b72fi
Tff7b72fi
T8b949e# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
Te6edf3FEATURE_NUMTff7b72=Tff7b72$(Tffa657printf Ta5d6ff"%03d" Ta5d6ff"Tff7b72$((T79c0ff10#Te6edf3$BRANCH_NUMBERTff7b72))Ta5d6ff"Tff7b72)
Te6edf3BRANCH_NAMETff7b72=Ta5d6ff"Tffd700${Te6edf3FEATURE_NUMTffd700}Ta5d6ff-Tffd700${Te6edf3BRANCH_SUFFIXTffd700}Ta5d6ff"
Tff7b72fi
T8b949e# GitHub enforces a 244-byte limit on branch names
T8b949e# Validate and truncate if necessary
Te6edf3MAX_BRANCH_LENGTHTff7b72=T79c0ff244
Tff7b72if Tff7b72[ Tffd700${#Te6edf3BRANCH_NAMETffd700} -gt Te6edf3$MAX_BRANCH_LENGTH Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Calculate how much we need to trim from suffix
T8b949e# Account for prefix length: timestamp (15) + hyphen (1) = 16, or sequential (3) + hyphen (1) = 4
Te6edf3PREFIX_LENGTHTff7b72=Tff7b72$(( Tffd700${#Te6edf3FEATURE_NUMTffd700} Tff7b72+ T79c0ff1 Tff7b72))
Te6edf3MAX_SUFFIX_LENGTHTff7b72=Tff7b72$((Te6edf3MAX_BRANCH_LENGTH Tff7b72- Te6edf3PREFIX_LENGTHTff7b72))
T8b949e# Truncate suffix at word boundary if possible
Te6edf3TRUNCATED_SUFFIXTff7b72=Tff7b72$(Tffa657echo Ta5d6ff"Te6edf3$BRANCH_SUFFIXTa5d6ff" Tb4b4b4| cut -c1-Te6edf3$MAX_SUFFIX_LENGTHTff7b72)
T8b949e# Remove trailing hyphen if truncation created one
Te6edf3TRUNCATED_SUFFIXTff7b72=Tff7b72$(Tffa657echo Ta5d6ff"Te6edf3$TRUNCATED_SUFFIXTa5d6ff" Tb4b4b4| sed Ta5d6ff's/-$//'Tff7b72)
Te6edf3ORIGINAL_BRANCH_NAMETff7b72=Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff"
Te6edf3BRANCH_NAMETff7b72=Ta5d6ff"Tffd700${Te6edf3FEATURE_NUMTffd700}Ta5d6ff-Tffd700${Te6edf3TRUNCATED_SUFFIXTffd700}Ta5d6ff"
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ff[specify] Original: Te6edf3$ORIGINAL_BRANCH_NAMETa5d6ff (Tffd700${#Te6edf3ORIGINAL_BRANCH_NAMETffd700}Ta5d6ff bytes)Ta5d6ff"
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ff[specify] Truncated to: Te6edf3$BRANCH_NAMETa5d6ff (Tffd700${#Te6edf3BRANCH_NAMETffd700}Ta5d6ff bytes)Ta5d6ff"
Tff7b72fi
Te6edf3FEATURE_DIRTff7b72=Ta5d6ff"Te6edf3$SPECS_DIRTa5d6ff/Te6edf3$BRANCH_NAMETa5d6ff"
Te6edf3SPEC_FILETff7b72=Ta5d6ff"Te6edf3$FEATURE_DIRTa5d6ff/spec.mdTa5d6ff"
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" !Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$HAS_GITTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3branch_create_errorTff7b72=Ta5d6ff""
Tff7b72if ! Te6edf3branch_create_errorTff7b72=Tff7b72$(git checkout -q -b Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" T79c0ff2>Tb4b4b4&T79c0ff1Tff7b72)Tb4b4b4; Tff7b72then
Te6edf3current_branchTff7b72=Ta5d6ff"Tff7b72$(git rev-parse --abbrev-ref HEAD T79c0ff2>/dev/null Tff7b72|| Tffa657trueTff7b72)Ta5d6ff"
T8b949e# Check if branch already exists
Tff7b72if git branch --list Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" Tb4b4b4| grep -q .Tb4b4b4; Tff7b72then
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$ALLOW_EXISTINGTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# If we're already on the branch, continue without another checkout.
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$current_branchTa5d6ff" Tff7b72= Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
:
T8b949e# Otherwise switch to the existing branch instead of failing.
Tff7b72elif ! Te6edf3switch_branch_errorTff7b72=Tff7b72$(git checkout -q Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" T79c0ff2>Tb4b4b4&T79c0ff1Tff7b72)Tb4b4b4; Tff7b72then
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ffError: Failed to switch to existing branch 'Te6edf3$BRANCH_NAMETa5d6ff'. Please resolve any local changes or conflicts and try again.Ta5d6ff"
Tff7b72if Tff7b72[ -n Ta5d6ff"Te6edf3$switch_branch_errorTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
>Tb4b4b4&T79c0ff2 Tffa657printf Ta5d6ff'%s\n' Ta5d6ff"Te6edf3$switch_branch_errorTa5d6ff"
Tff7b72fi
Tffa657exit T79c0ff1
Tff7b72fi
Tff7b72elif Tff7b72[ Ta5d6ff"Te6edf3$USE_TIMESTAMPTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ffError: Branch 'Te6edf3$BRANCH_NAMETa5d6ff' already exists. Rerun to get a new timestamp or use a different --short-name.Ta5d6ff"
Tffa657exit T79c0ff1
Tff7b72else
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ffError: Branch 'Te6edf3$BRANCH_NAMETa5d6ff' already exists. Please use a different feature name or specify a different number with --number.Ta5d6ff"
Tffa657exit T79c0ff1
Tff7b72fi
Tff7b72else
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ffError: Failed to create git branch 'Te6edf3$BRANCH_NAMETa5d6ff'.Ta5d6ff"
Tff7b72if Tff7b72[ -n Ta5d6ff"Te6edf3$branch_create_errorTa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
>Tb4b4b4&T79c0ff2 Tffa657printf Ta5d6ff'%s\n' Ta5d6ff"Te6edf3$branch_create_errorTa5d6ff"
Tff7b72else
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Please check your git configuration and try again."
Tff7b72fi
Tffa657exit T79c0ff1
Tff7b72fi
Tff7b72fi
Tff7b72else
>Tb4b4b4&T79c0ff2 Tffa657echo Ta5d6ff"Ta5d6ff[specify] Warning: Git repository not detected; skipped branch creation for Te6edf3$BRANCH_NAMETa5d6ff"
Tff7b72fi
mkdir -p Ta5d6ff"Te6edf3$FEATURE_DIRTa5d6ff"
Tff7b72if Tff7b72[ ! -f Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3TEMPLATETff7b72=Tff7b72$(resolve_template Ta5d6ff"spec-template" Ta5d6ff"Te6edf3$REPO_ROOTTa5d6ff"Tff7b72) Tff7b72|| Tffa657true
Tff7b72if Tff7b72[ -n Ta5d6ff"Te6edf3$TEMPLATETa5d6ff" Tff7b72] Tff7b72&& Tff7b72[ -f Ta5d6ff"Te6edf3$TEMPLATETa5d6ff" Tff7b72]Tb4b4b4; Tff7b72then
cp Ta5d6ff"Te6edf3$TEMPLATETa5d6ff" Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff"
Tff7b72else
Tffa657echo Ta5d6ff"Warning: Spec template not found; created empty spec file" >Tb4b4b4&T79c0ff2
touch Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff"
Tff7b72fi
Tff7b72fi
T8b949e# Inform the user how to persist the feature variable in their own shell
Tffa657printf Ta5d6ff'# To persist: export SPECIFY_FEATURE=%q\n' Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" >Tb4b4b4&T79c0ff2
Tff7b72fi
Tff7b72if Te6edf3$JSON_MODETb4b4b4; Tff7b72then
Tff7b72if Tffa657command -v jq >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
jq -cn Tffea00\
--arg branch_name Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" Tffea00\
--arg spec_file Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff" Tffea00\
--arg feature_num Ta5d6ff"Te6edf3$FEATURE_NUMTa5d6ff" Tffea00\
Ta5d6ff'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num,DRY_RUN:true}'
Tff7b72else
jq -cn Tffea00\
--arg branch_name Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff" Tffea00\
--arg spec_file Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff" Tffea00\
--arg feature_num Ta5d6ff"Te6edf3$FEATURE_NUMTa5d6ff" Tffea00\
Ta5d6ff'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}'
Tff7b72fi
Tff7b72else
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
Tffa657printf Ta5d6ff'{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$FEATURE_NUMTa5d6ff"Tff7b72)Ta5d6ff"
Tff7b72else
Tffa657printf Ta5d6ff'{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$SPEC_FILETa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$FEATURE_NUMTa5d6ff"Tff7b72)Ta5d6ff"
Tff7b72fi
Tff7b72fi
Tff7b72else
Tffa657echo Ta5d6ff"Ta5d6ffBRANCH_NAME: Te6edf3$BRANCH_NAMETa5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ffSPEC_FILE: Te6edf3$SPEC_FILETa5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ffFEATURE_NUM: Te6edf3$FEATURE_NUMTa5d6ff"
Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$DRY_RUNTa5d6ff" !Tff7b72= Tffa657true Tff7b72]Tb4b4b4; Tff7b72then
Tffa657printf Ta5d6ff'# To persist in your shell: export SPECIFY_FEATURE=%q\n' Ta5d6ff"Te6edf3$BRANCH_NAMETa5d6ff"
Tff7b72fi
Tff7b72fi
Served by rngit 1.5.2 - Generated in 0.1s